#include "common.h"
#include "msn.h"
#include "msn_command.h"
#include "msn_contacts.h"
#include "sock.h"
#include "sock_ssl.h"
#include "msn_user.h"

static const char *msn_ns_hostname = "messenger.hotmail.com";
static const int msn_ns_port = 1863;

static void msn_ns_connect_done(int fd, int error, int res, void *data);
static void msn_connect_finish(int fd, int error, int res, void *arg);
static void msn_ns_cmd_send_finish(int fd, int error, int res, void *arg);
/* usr */
static void msn_usr_handle(const char *line, const int size, msn_session_t *s);
/* ADL */
static void msn_adl_send_finish(int fd, int error, int res, void *arg);
static void msn_ns_adl_handle_done(int fd, int error, int res, void *data) ;
static void msn_ns_adl_handle(msn_session_t *robot, const char *buf, const int size);
/* PRP */
static void msn_prp_send(msn_session_t *robot);
/* CHG */
static void msn_chg_send(msn_session_t *robot);
/* PNG */
static void msn_png_send(int fd, short flag, void *data);
/* chl */
static void msn_chl_handle(char *line, const int size, msn_session_t *robot);
static void msn_chl_handle_done(int fd, int error, int res, void *data) ;
/* QNG */
static void msn_qng_handle(char *line, const int size, msn_session_t *robot);
/* XFR */
static void msn_xfr_handle(const char *line, const int size, msn_session_t *ms);
/* a is security_token_t, b is string */
static int security_comp(const void *a, const void *b);

static int security_comp(const void *a, const void *b)
{
	const char *str = b;
	const security_token_t *t = a;

	return strcasecmp(t->domain->str, b);
}

void msn_init()
{
	msn_session_t *ms = NULL;

	/* init ssl cert */
	sslcontext = sock_ssl_init();
	ms = msn_session_new(conf->msn.user, conf->msn.password);
	msn_ns_connect(ms);
}

void msn_read_from_ns(int fd, short flag, void *arg)
{
	msn_session_t *robot = arg;
	gchar readbuf[MAX_BUFFER];
	gchar linebuf[MAX_BUFFER];
	gint size_read = -1;
	gint size_handled = 0;
	GString *data = NULL;
	int exit_loop = 0;

	if((size_read = read(fd, readbuf, MAX_BUFFER)) == -1) {
		error_log(errno, "fd %d read failed\n", fd);
		/* retry connect too */
		return ;
	}
	if(size_read == 0) {
		/* connection has been reset, how to do */
		debug_log(5, "msn ns close connection %d\n", fd);
		return;
	}
	g_string_append_len(robot->ns->in, readbuf, size_read);
	debug_log(1, "%d bytes, {%s}\n", robot->ns->in->len, robot->ns->in->str);
	data = robot->ns->in;
	/* parse one line from ns */
	for(; size_handled < data->len && exit_loop == 0; ) {
		int line_size = msn_line_get(data->str + size_handled, data->len - size_handled, \
						linebuf, MAX_BUFFER - 1);
		if(line_size == 0) {
			debug_log(9, "%s", "can't receive a fully command from ns\n");
			break;
		}
		size_handled += line_size;
		switch(msn_ns_command_type_get(linebuf, line_size)) {
			case MSN_NS_MSG: {
				char *digit = strrchr(linebuf, ' ');
				if(digit) {
					int msg_len = atoi(digit + 1);
					debug_log(9, "MSG length %d\n", msg_len);
					size_handled += msg_len;
					if(size_handled >= data->len)
						if(robot->owner->statu != MSN_USER_STATU_NLN)
							msn_command_send(robot->ns->ev, robot->ns->fd, robot, \
											msn_ns_cmd_send_finish, \
											"BLP %d AL\r\n", robot->tr_id++);
				}
				break;
			}
			case MSN_NS_BLP:
				/* begin to send GTC */
				break;
			case MSN_NS_USR:
				msn_usr_handle(linebuf, line_size, robot);
				break;
			case MSN_NS_ADL: {
				int payload = 0;
				msn_ns_adl_parse(linebuf, line_size, &payload);
				if(payload == 0 && robot->owner->statu != MSN_USER_STATU_NLN) 
					msn_prp_send(robot);
				else {
					/* server adl in */
					if(payload + size_handled < data->len) {
						size_handled -= line_size;
						exit_loop = 1;
						break;
					}
					msn_ns_adl_handle(robot, data->str + size_handled, payload);
					size_handled += payload;
				}
				break;
			 }
			case MSN_NS_PRP:
				msn_chg_send(robot);
				break;
			case MSN_NS_UBX: {
				int payload = msn_command_payload(linebuf, line_size);
				if(payload > 0) {
					if(payload + size_handled <= data->len)
						size_handled += payload;
					else {
						size_handled -= payload;
						exit_loop = 1;
					}
				}
				break;
			}
			case MSN_NS_CHG:
				/* receive CHG */
				robot->owner->statu = MSN_USER_STATU_NLN;
				break;
			case MSN_NS_RNG:
				/* ready to chat */
				msn_rng_handle(linebuf, line_size, robot);
				break;
			case MSN_NS_CHL:
				msn_chl_handle(linebuf, line_size, robot);
				break;
			case MSN_NS_QRY: {
				/* set robot timer */
				struct timeval tv = {30, 0};
				xevtimer_set(robot->ns->timer, msn_png_send, robot);
				xevtimer_add(robot->ns->timer, &tv);
				break;
			}
			case MSN_NS_QNG: 
				msn_qng_handle(linebuf, line_size, robot);
				break;
			case MSN_NS_VER:
				msn_command_send(robot->ns->ev, robot->ns->fd, robot, msn_ns_cmd_send_finish, \
								"CVR %d 0x0804 winnt 5.2 i386 MSNMSGR 8.1.0178 MSFT %s\r\n", \
								robot->tr_id++, robot->name->str);
				break;
			case MSN_NS_CVR:
				msn_command_send(robot->ns->ev, robot->ns->fd, robot, msn_ns_cmd_send_finish, \
								"USR %d SSO I %s\r\n", robot->tr_id++, robot->name->str);
				break;
			case MSN_NS_XFR:
				msn_xfr_handle(linebuf, line_size, robot);
				break;
			case MSN_NS_GCF:
			{
				int payload = msn_command_payload(linebuf, line_size);
				if(data->len < payload + size_handled) {
					size_handled -= line_size;
					exit_loop = 1;
				}
				else
					size_handled += payload;
				break;
			}
			default:
				break;
		}
		debug_log(9, "Handled %d bytes\n", size_handled);
	}
	/* reset ns_in */
	if(size_handled < data->len) {
		GString *new_in = g_string_new_len(data->str + size_handled, data->len - size_handled);
		g_string_free(data, TRUE);
		robot->ns->in = new_in;
	}
	else 
		g_string_truncate(robot->ns->in, 0);
	debug_log(9, "left %d bytes\n", robot->ns->in->len);
}


int msn_line_get(const char *buf, int size, char *retv, int ret_size)
{
	if(!buf)
		return 0;
	if(size <= 0)
		return 0;
	
	char *start, *end;
	int line_size = 0;

	start = (char *)buf;
	end = strstr(start, "\r\n");
	if(!end) {
		*retv = '\0';
		return 0;
	}
	/* include /r/n */
	line_size = end - start + 2;
	assert(ret_size > line_size);
	memcpy(retv, start, line_size);
	retv[line_size] = '\0';
	debug_log(9, "line %d bytes {%s}\n", line_size, retv);
	return line_size;
}

security_token_t *security_token_init(void)
{
	security_token_t *token = g_slice_new(security_token_t);

	if(!token)
		return NULL;
	return token;
}

void security_token_clean(security_token_t *token)
{
	if(!token)
		return ;
	g_string_free(token->domain, 1);
	g_string_free(token->uri, 1);
	g_string_free(token->ticket, 1);
	g_string_free(token->binary, 1);
	g_slice_free(security_token_t, token);
}

security_token_t *security_token_find_by_domain(GList *tokens, const char *domain)
{
	if(!tokens || !domain)
		return NULL;
	int i = 0;
	int len = 0;

	len = g_list_length(tokens);
	debug_log(9, "token length %d, need to find %s\n", len, domain);
	for(i = 0; i < len; i++ ) {
		security_token_t *token = g_list_nth_data(tokens, i);
		g_assert(token);
		g_assert(token->domain);
		if(!strcasecmp(domain, token->domain->str)) {
			debug_log(5, "find %p\n", token);
			return token;
		}
	}
	return NULL;
}

void msn_adl_send(msn_session_t *robot)
{
	if(!robot)
		return;

	GString *xml;
	GString *out = g_string_new(NULL);

	xml = msn_adl_xml_gen(robot);
	assert(xml);
	g_string_printf(out, "ADL %d %d\r\n", robot->tr_id++, xml->len);
	g_string_append_len(out, xml->str, xml->len);
	debug_log(8, "begin to send {%s}\n", out->str);
	sock_write(robot->ns->ev, robot->ns->fd, msn_ns_cmd_send_finish, robot, out);
	g_string_free(xml, 1);
	g_string_free(out, 1);
}

static void msn_prp_send(msn_session_t *ms)
{
	g_assert(ms);
	msn_command_send(ms->ns->ev, ms->ns->fd, ms, msn_ns_cmd_send_finish, "PRP %d MFN %s\r\n", 
					ms->tr_id++, msn_url_encode(conf->msn.display_name));
}

/* CHG */
static void msn_chg_send(msn_session_t *ms)
{
	g_assert(ms);
	msn_command_send(ms->ns->ev, ms->ns->fd, ms, msn_ns_cmd_send_finish, "CHG %d NLN 2253180972\r\n", 
					ms->tr_id++);
	ms->owner->statu = MSN_USER_STATU_NLN;
}

static void msn_chl_handle(char *line, const int size, msn_session_t *robot)
{
	assert(line && robot);
	char challenge[32];
	char *token, *last;
	const char *sep = W_SPACE;
	int i = 0;
	GString *out = g_string_new(NULL);
	
	for(token = strtok_r(line, sep, &last); token; token = strtok_r(NULL, sep, &last), i++) {
		if(i == 2) 
			msn_chl_challenge(token, challenge);
	}
	g_string_printf(out, "QRY %d %s 32\r\n%s", robot->tr_id++, PRODUCT_ID, challenge);
	debug_log(9, "{%s}\n", out->str);
	sock_write(robot->ns->ev, robot->ns->fd, msn_ns_cmd_send_finish, robot, out);
	g_string_free(out, 1);
}

static void msn_ns_adl_handle(msn_session_t *robot, const char *buf, const int size)
{
	msn_user_t *fri;

	if(!robot || !buf) 
		return ;
	fri = msn_adl_xml_parse(robot, buf, size);
	if(fri == NULL)
		return;
	GString *out = msn_adl_xml_gen_one(fri);
	if(!out) 
		return;
	debug_log(9, "Ready to send {%s}\n", out->str);
	sock_write(robot->ns->ev, robot->ns->fd, msn_ns_cmd_send_finish, robot, out);
	g_string_free(out, 1);
}


static void msn_png_send(int fd, short flag, void *data)
{
	msn_session_t *robot = data;
	if(!robot)
		return ;
	GString *out = NULL;
	
	out = g_string_new(NULL);
	g_string_printf(out, "PNG\r\n");
	assert(out);
	sock_write(robot->ns->timer, robot->ns->fd, msn_ns_cmd_send_finish, robot, out);
	g_string_free(out, 1);
}

static void msn_ns_cmd_send_finish(int fd, int error, int res, void *arg)
{
	msn_session_t *robot = arg;

	assert(robot);
	if(error) {
		error_log(errno, "failed to send cmd to ns\n");
		/* reset session */
		return;
	}
	xevent_update(robot->ns->ev, SOCK_FLAG_READ, robot, msn_read_from_ns);
}

static void msn_qng_handle(char *line, const int size, msn_session_t *robot)
{
	if(!line || !robot)
		return;
	int time = 50;
	struct timeval tv;

	msn_ns_qng_parse(line, size, &time);
	tv.tv_sec = time;
	tv.tv_usec = 0;
	xevtimer_update(robot->ns->timer, &tv, robot, msn_png_send);
}

gchar *msn_url_encode(const gchar *str)
{
	const char *iter;
	static char buf[MAX_BUFFER];
	char utf_char[6];
	guint i, j = 0;

	g_return_val_if_fail(str != NULL, NULL);
	g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);

	iter = str;
	for (; *iter && j < (MAX_BUFFER - 1) ; iter = g_utf8_next_char(iter)) {
		gunichar c = g_utf8_get_char(iter);
		/* If the character is an ASCII character and is alphanumeric
		 * no need to escape */
		if (c < 128 && isalnum(c)) {
			buf[j++] = c;
		} else {
			int bytes = g_unichar_to_utf8(c, utf_char);
			for (i = 0; i < bytes; i++) {
				if (j > (MAX_BUFFER - 4))
					break;
				sprintf(buf + j, "%%%02x", utf_char[i] & 0xff);
				j += 3;
			}
		}
	}

	buf[j] = '\0';

	return buf;
}

gchar *msn_url_decode(const gchar *str) 
{
	static char buf[MAX_BUFFER];
	guint i, j = 0;
	char *bum;
	char hex[3];

	g_return_val_if_fail(str != NULL, NULL);

	/*
	 * XXX - This check could be removed and buf could be made
	 * dynamically allocated, but this is easier.
	 */
	if (strlen(str) >= MAX_BUFFER)
		return NULL;

	for (i = 0; i < strlen(str); i++) {

		if (str[i] != '%')
			buf[j++] = str[i];
		else {
			strncpy(hex, str + ++i, 2);
			hex[2] = '\0';

			/* i is pointing to the start of the number */
			i++;

			/*
			 * Now it's at the end and at the start of the for loop
			 * will be at the next character.
			 */
			buf[j++] = strtol(hex, NULL, 16);
		}
	}

	buf[j] = '\0';

	if (!g_utf8_validate(buf, -1, (const char **)&bum))
		*bum = '\0';

	return buf;
}

void msn_ns_connect(msn_session_t *ms)
{
	g_return_if_fail(ms!=NULL);
	g_return_if_fail(ms->connected != 1);
	struct sockaddr_in srv;

    srv.sin_family = AF_INET;
    srv.sin_port = htons(ms->ns->port);
    srv.sin_addr = ms->ns->addr;
    sock_connect(ms->ns->ev, ms->ns->fd, &srv, ms, msn_ns_connect_done);
}

static void msn_ns_connect_done(int fd, int error, int res, void *data)
{
	msn_session_t *s = data;
	if(error) {
		debug_log(0, "fd %d connect msn notify server failed\n", fd);
		msn_ns_connect(s);
		return;
	}
	debug_log(5, "fd %d connected %s success\n", fd, MSN_NOTIFY_SERVER);
	s->connected = 1;
	/* begin to send ver */
	msn_command_send(s->ns->ev, s->ns->fd, s, msn_ns_cmd_send_finish,\
				   	"VER %d MSNP15 MSNP14 MSNP13 CVR0\r\n", s->tr_id++);
}

static void msn_xfr_handle(const char *line, const int size, msn_session_t *ms)
{
	g_return_if_fail(line != NULL && ms!=NULL);
	char ip[16];
	int port = 0;
	int isns = 0;

	msn_ns_xfr_parse(line, size, &isns, ip, &port);
	if(isns) {
		struct in_addr addr;
		int fd;

		msn_ns_clean(ms->ns);
		ms->connected = 0;
		fd = sock_open_from_host(ip, &addr);
		ms->ns = msn_ns_new(fd, port, addr);
		msn_ns_connect(ms);
	}
	else {
	}
}

static void msn_usr_handle(const char *line, const int size, msn_session_t *s)
{
	g_return_if_fail(line != NULL && s != NULL);
	char nonce[128];

	nonce[0] = '\0';
	msn_ns_usr_parse(line, size, nonce);
	if(nonce[0] != '\0') {
		s->nonce = g_string_new(nonce);
		msn_sso_start(s);
	}
	else {
		/* begin to get address list */
		msn_membership_get(s);
	}
}
